xref: /PHP-7.1/win32/build/mkdist.php (revision 7f6387b5)
1<?php # $Id$
2/* piece together a windows binary distro */
3
4$build_dir = $argv[1];
5$php_build_dir = $argv[2];
6$phpdll = $argv[3];
7$sapi_targets = explode(" ", $argv[4]);
8$ext_targets = explode(" ", $argv[5]);
9$pecl_targets = explode(" ", $argv[6]);
10$snapshot_template = $argv[7];
11
12$is_debug = preg_match("/^debug/i", $build_dir);
13
14echo "Making dist for $build_dir\n";
15
16$dist_dir = $build_dir . "/php-" . phpversion();
17$test_dir = $build_dir . "/php-test-pack-" . phpversion();
18$pecl_dir = $build_dir . "/pecl-" . phpversion();
19
20@mkdir($dist_dir);
21@mkdir("$dist_dir/ext");
22@mkdir("$dist_dir/dev");
23@mkdir("$dist_dir/extras");
24@mkdir($pecl_dir);
25
26/* figure out additional DLL's that are required */
27$extra_dll_deps = array();
28$per_module_deps = array();
29$pecl_dll_deps = array();
30
31function get_depends($module)
32{
33	static $no_dist = array(
34		/* windows system dlls that should not be bundled */
35		'advapi32.dll', 'comdlg32.dll', 'crypt32.dll', 'gdi32.dll', 'kernel32.dll', 'ntdll.dll',
36		'odbc32.dll', 'ole32.dll', 'oleaut32.dll', 'rpcrt4.dll',
37		'shell32.dll', 'shlwapi.dll', 'user32.dll', 'ws2_32.dll', 'ws2help.dll',
38		'comctl32.dll', 'winmm.dll', 'wsock32.dll', 'winspool.drv', 'msasn1.dll',
39		'secur32.dll', 'netapi32.dll', 'dnsapi.dll', 'psapi.dll', 'normaliz.dll',
40		'iphlpapi.dll',
41
42		/* apache */
43		'apachecore.dll',
44
45		/* apache 2 */
46		'libhttpd.dll', 'libapr.dll', 'libaprutil.dll','libapr-1.dll', 'libaprutil-1.dll',
47
48		/* pi3web */
49		'piapi.dll', 'pi3api.dll',
50
51		/* nsapi */
52		'ns-httpd30.dll', 'ns-httpd35.dll', 'ns-httpd36.dll', 'ns-httpd40.dll',
53
54		/* oracle */
55		'oci.dll', 'ociw32.dll',
56
57		/* sybase */
58		'libcs.dll', 'libct.dll',
59
60		/* firebird */
61		'fbclient.dll',
62
63		/* visual C++; mscvrt.dll is present on everyones system,
64		 * but the debug version (msvcrtd.dll) and those from visual studio.net
65		 * (msvcrt7x.dll) are not */
66		'msvcrt.dll',
67		'msvcr90.dll',
68		'wldap32.dll',
69		'vcruntime140.dll',
70		'msvcp140.dll',
71		);
72	static $no_dist_re = array(
73		"api-ms-win-crt-.+\.dll",
74	);
75	global $build_dir, $extra_dll_deps, $ext_targets, $sapi_targets, $pecl_targets, $phpdll, $per_module_deps, $pecl_dll_deps;
76
77	$bd = strtolower(realpath($build_dir));
78
79	$is_pecl = in_array($module, $pecl_targets);
80
81	$cmd = "$GLOBALS[build_dir]\\deplister.exe \"$module\" \"$GLOBALS[build_dir]\"";
82	$proc = proc_open($cmd,
83			array(1 => array("pipe", "w")),
84			$pipes);
85
86	$n = 0;
87	while (($line = fgetcsv($pipes[1]))) {
88		$n++;
89
90		$dep = strtolower($line[0]);
91		$depbase = basename($dep);
92		/* ignore stuff in our build dir, but only if it is
93	     * one of our targets */
94		if (((in_array($depbase, $sapi_targets) ||
95			   	in_array($depbase, $ext_targets) || in_array($depbase, $pecl_targets)) ||
96				$depbase == $phpdll) && file_exists($GLOBALS['build_dir'] . "/$depbase")) {
97			continue;
98		}
99		/* ignore some well-known system dlls */
100		if (in_array(basename($dep), $no_dist)) {
101			continue;
102		} else {
103			$skip = false;
104			foreach ($no_dist_re as $re) {
105				if (preg_match(",$re,", basename($dep)) > 0) {
106					$skip = true;
107					break;
108				}
109			}
110			if ($skip) {
111				continue;
112			}
113		}
114
115		if ($is_pecl) {
116			if (!in_array($dep, $pecl_dll_deps)) {
117				$pecl_dll_deps[] = $dep;
118			}
119		} else {
120			if (!in_array($dep, $extra_dll_deps)) {
121				$extra_dll_deps[] = $dep;
122			}
123		}
124
125		if (!isset($per_module_deps[basename($module)]) || !in_array($dep, $per_module_deps[basename($module)])) {
126			$per_module_deps[basename($module)][] = $dep;
127			//recursively check dll dependencies
128			get_depends($dep);
129		}
130	}
131	fclose($pipes[1]);
132	proc_close($proc);
133//echo "Module $module [$n lines]\n";
134}
135
136function copy_file_list($source_dir, $dest_dir, $list)
137{
138	global $is_debug, $dist_dir;
139
140	foreach ($list as $item) {
141		if (empty($item)) {
142			continue;
143		} elseif (!is_file($source_dir . DIRECTORY_SEPARATOR . $item)) {
144			echo "WARNING: $item not found\n";
145			continue;
146		}
147
148		echo "Copying $item from $source_dir to $dest_dir\n";
149		copy($source_dir . DIRECTORY_SEPARATOR . $item, $dest_dir . DIRECTORY_SEPARATOR . $item);
150		if ($is_debug) {
151			$itemdb = preg_replace("/\.(exe|dll|lib)$/i", ".pdb", $item);
152			if (file_exists("$source_dir/$itemdb")) {
153				copy("$source_dir/$itemdb", "$dist_dir/dev/$itemdb");
154			}
155		}
156		if (preg_match("/\.(exe|dll)$/i", $item)) {
157			get_depends($source_dir . '/' . $item);
158		}
159	}
160}
161
162function copy_text_file($source, $dest)
163{
164	$text = file_get_contents($source);
165	$text = preg_replace("/(\r\n?)|\n/", "\r\n", $text);
166	$fp = fopen($dest, "w");
167	fwrite($fp, $text);
168	fclose($fp);
169}
170
171/* very light-weight function to extract a single named file from
172 * a gzipped tarball.  This makes assumptions about the files
173 * based on the PEAR info set in $packages. */
174function extract_file_from_tarball($pkg, $filename, $dest_dir) /* {{{ */
175{
176	global $packages;
177
178	$name = $pkg . '-' . $packages[$pkg];
179	$tarball = $dest_dir . "/" . $name . '.tgz';
180	$filename = $name . '/' . $filename;
181	$destfilename = $dest_dir . "/" . basename($filename);
182
183	$fp = gzopen($tarball, 'rb');
184
185	$done = false;
186	do {
187		/* read the header */
188		$hdr_data = gzread($fp, 512);
189	   	if (strlen($hdr_data) == 0)
190			break;
191		$checksum = 0;
192		for ($i = 0; $i < 148; $i++)
193			$checksum += ord($hdr_data{$i});
194		for ($i = 148; $i < 156; $i++)
195			$checksum += 32;
196		for ($i = 156; $i < 512; $i++)
197			$checksum += ord($hdr_data{$i});
198
199		$hdr = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $hdr_data);
200
201		$hdr['checksum'] = octdec(trim($hdr['checksum']));
202
203		if ($hdr['checksum'] != $checksum) {
204			echo "Checksum for $tarball $hdr[filename] is invalid\n";
205			print_r($hdr);
206			return;
207		}
208
209		$hdr['size'] = octdec(trim($hdr['size']));
210		echo "File: $hdr[filename] $hdr[size]\n";
211
212		if ($filename == $hdr['filename']) {
213			echo "Found the file we want\n";
214			$dest = fopen($destfilename, 'wb');
215			$x = stream_copy_to_stream($fp, $dest, $hdr['size']);
216			fclose($dest);
217			echo "Wrote $x bytes into $destfilename\n";
218			break;
219		}
220
221		/* skip body of the file */
222		$size = 512 * ceil((int)$hdr['size'] / 512);
223		echo "Skipping $size bytes\n";
224		gzseek($fp, gztell($fp) + $size);
225
226	} while (!$done);
227
228} /* }}} */
229
230
231/* the core dll */
232copy("$build_dir/php.exe", "$dist_dir/php.exe");
233/* copy dll and its dependencies */
234copy_file_list($build_dir, "$dist_dir", [$phpdll]);
235
236/* and the .lib goes into dev */
237$phplib = str_replace(".dll", ".lib", $phpdll);
238copy("$build_dir/$phplib", "$dist_dir/dev/$phplib");
239/* debug builds; copy the symbols too */
240if ($is_debug) {
241	$phppdb = str_replace(".dll", ".pdb", $phpdll);
242	copy("$build_dir/$phppdb", "$dist_dir/dev/$phppdb");
243}
244/* copy the sapi */
245copy_file_list($build_dir, "$dist_dir", $sapi_targets);
246
247/* copy the extensions */
248copy_file_list($build_dir, "$dist_dir/ext", $ext_targets);
249
250/* pecl sapi and extensions */
251if(sizeof($pecl_targets)) {
252	copy_file_list($build_dir, $pecl_dir, $pecl_targets);
253}
254
255/* populate reading material */
256$text_files = array(
257	"LICENSE" => "license.txt",
258	"NEWS" => "news.txt",
259	"README.REDIST.BINS" => "readme-redist-bins.txt",
260	"php.ini-development" => "php.ini-development",
261	"php.ini-production" => "php.ini-production",
262	"win32/install.txt" => "install.txt",
263);
264
265foreach ($text_files as $src => $dest) {
266	copy_text_file($src, $dist_dir . '/' . $dest);
267}
268
269/* general other files */
270$general_files = array(
271	"php.gif"				=>	"php.gif",
272	"$GLOBALS[build_dir]\\deplister.exe"	=>	"deplister.exe",
273);
274
275foreach ($general_files as $src => $dest) {
276	copy($src, $dist_dir . '/' . $dest);
277}
278
279/* include a snapshot identifier */
280$branch = "HEAD"; // TODO - determine this from SVN branche name
281$fp = fopen("$dist_dir/snapshot.txt", "w");
282$now = date("r");
283$version = phpversion();
284fwrite($fp, <<<EOT
285This snapshot was automatically generated on
286$now
287
288Version: $version
289Branch: $branch
290Build: $build_dir
291
292EOT
293);
294/* list build-in extensions */
295$exts = get_loaded_extensions();
296fprintf($fp, "\r\nBuilt-in Extensions\r\n");
297fwrite($fp, "===========================\r\n");
298foreach ($exts as $ext) {
299	fprintf($fp, "%s\r\n", $ext);
300}
301fwrite($fp, "\r\n\r\n");
302
303/* list dependencies */
304fprintf($fp, "Dependency information:\r\n");
305foreach ($per_module_deps as $modulename => $deps) {
306	if (in_array($modulename, $pecl_targets))
307		continue;
308
309	fprintf($fp, "Module: %s\r\n", $modulename);
310	fwrite($fp, "===========================\r\n");
311	foreach ($deps as $dll) {
312		fprintf($fp, "\t%s\r\n", basename($dll));
313	}
314	fwrite($fp, "\r\n");
315}
316fclose($fp);
317
318/* Now add those dependencies */
319foreach ($extra_dll_deps as $dll) {
320	if (!file_exists($dll)) {
321		/* try template dir */
322		$tdll = $snapshot_template . "/dlls/" . basename($dll);
323		if (!file_exists($tdll)) {
324			$tdll = $php_build_dir . '/bin/' . basename($dll);
325			if (!file_exists($tdll)) {
326				echo "WARNING: distro depends on $dll, but could not find it on your system\n";
327				continue;
328			}
329		}
330		$dll = $tdll;
331	}
332	copy($dll, "$dist_dir/" . basename($dll));
333}
334
335/* TODO:
336add sanity check and test if all required DLLs are present, per version
337This version works at least for 3.6, 3.8 and 4.0 (5.3-vc6, 5.3-vc9 and HEAD).
338Add ADD_DLLS to add extra DLLs like dynamic dependencies for standard
339deps. For example, libenchant.dll loads libenchant_myspell.dll or
340libenchant_ispell.dll
341*/
342$ENCHANT_DLLS = array(
343	array('', 'glib-2.dll'),
344	array('', 'gmodule-2.dll'),
345	array('lib/enchant', 'libenchant_myspell.dll'),
346	array('lib/enchant', 'libenchant_ispell.dll'),
347);
348foreach ($ENCHANT_DLLS as $dll) {
349	$dest  = "$dist_dir/$dll[0]";
350	$filename = $dll[1];
351
352	if (!file_exists("$dest") || !is_dir("$dest")) {
353		if (!mkdir("$dest", 0777, true)) {
354			echo "WARNING: couldn't create '$dest' for enchant plugins ";
355		}
356	}
357
358	if (!copy($php_build_dir . '/bin/' . $filename, "$dest/" . basename($filename))) {
359			echo "WARNING: couldn't copy $filename into the dist dir";
360	}
361}
362
363$SASL_DLLS = $php_build_dir . "/bin/sasl2/sasl*.dll";
364$fls = glob($SASL_DLLS);
365if (!empty($fls)) {
366	$sasl_dest_dir = "$dist_dir/sasl2";
367	if (!file_exists($sasl_dest_dir) || !is_dir($sasl_dest_dir)) {
368		if (!mkdir("$sasl_dest_dir", 0777, true)) {
369			echo "WARNING: couldn't create '$sasl_dest_dir' for SASL2 auth plugins ";
370		}
371	}
372	foreach ($fls as $fl) {
373		if (!copy($fl, "$sasl_dest_dir/" . basename($fl))) {
374			echo "WARNING: couldn't copy $fl into the $sasl_dest_dir";
375		}
376	}
377}
378
379/* and those for pecl */
380foreach ($pecl_dll_deps as $dll) {
381	if (in_array($dll, $extra_dll_deps)) {
382		/* already in main distro */
383		continue;
384	}
385	if (!file_exists($dll)) {
386		/* try template dir */
387		$tdll = $snapshot_template . "/dlls/" . basename($dll);
388		if (!file_exists($tdll)) {
389			echo "WARNING: distro depends on $dll, but could not find it on your system\n";
390			continue;
391		}
392		$dll = $tdll;
393	}
394	copy($dll, "$pecl_dir/" . basename($dll));
395}
396
397function copy_dir($source, $dest)
398{
399	if (!is_dir($dest)) {
400		if (!mkdir($dest)) {
401			return false;
402		}
403	}
404
405	$d = opendir($source);
406	while (($f = readdir($d)) !== false) {
407		if ($f == '.' || $f == '..' || $f == '.svn') {
408			continue;
409		}
410		$fs = $source . '/' . $f;
411		$fd = $dest . '/' . $f;
412		if (is_dir($fs)) {
413			copy_dir($fs, $fd);
414		} else {
415			copy($fs, $fd);
416		}
417	}
418	closedir($d);
419}
420
421
422
423function copy_test_dir($directory, $dest)
424{
425	if(substr($directory,-1) == '/') {
426		$directory = substr($directory,0,-1);
427	}
428
429	if ($directory == 'tests' || $directory == 'examples') {
430		if (!is_dir($dest . '/tests')) {
431			mkdir($dest . '/tests', 0775, true);
432		}
433		copy_dir($directory, $dest . '/tests/');
434
435		return false;
436	}
437
438	if(!file_exists($directory) || !is_dir($directory)) {
439		echo "failed... $directory\n";
440		return FALSE;
441	}
442
443	$directory_list = opendir($directory);
444
445	while (FALSE !== ($file = readdir($directory_list))) {
446		$full_path = $directory . '/' . $file;
447		if($file != '.' && $file != '..' && $file != '.svn' && is_dir($full_path)) {
448			if ($file == 'tests' || $file == 'examples') {
449				if (!is_dir($dest . '/' . $full_path)) {
450					mkdir($dest . '/' . $full_path , 0775, true);
451				}
452				copy_dir($full_path, $dest . '/' . $full_path . '/');
453				continue;
454			} else {
455				copy_test_dir($full_path, $dest);
456			}
457		}
458	}
459
460	closedir($directory_list);
461}
462
463function make_phar_dot_phar($dist_dir)
464{
465	if (!extension_loaded('phar')) {
466		return;
467	}
468
469	$path_to_phar = realpath(__DIR__ . '/../../ext/phar');
470
471	echo "Generating pharcommand.phar\n";
472	$phar = new Phar($dist_dir . '/pharcommand.phar', 0, 'pharcommand');
473
474	foreach (new DirectoryIterator($path_to_phar . '/phar') as $file) {
475		if ($file->isDir() || $file == 'phar.php') {
476			continue;
477		}
478
479		echo 'adding ', $file, "\n";
480		$phar[(string) $file] = file_get_contents($path_to_phar.  '/phar/' . $file);
481	}
482
483	$phar->setSignatureAlgorithm(Phar::SHA1);
484	$stub = file($path_to_phar . '/phar/phar.php');
485
486	unset($stub[0]); // remove hashbang
487	$phar->setStub(implode('', $stub));
488
489	echo "Creating phar.phar.bat\n";
490	file_put_contents($dist_dir . '/phar.phar.bat', "\"%~dp0php.exe\" \"%~dp0pharcommand.phar\" %*\r\n");
491}
492
493if (!is_dir($test_dir)) {
494	mkdir($test_dir);
495}
496
497$dirs = array(
498	'ext',
499	'Sapi',
500	'Zend',
501	'tests'
502);
503foreach ($dirs as $dir) {
504	copy_test_dir($dir, $test_dir);
505}
506copy('run-tests.php', $test_dir . '/run-test.php');
507
508/* change this next line to true to use good-old
509 * hand-assembled go-pear-bundle from the snapshot template */
510$use_pear_template = true;
511
512if (!$use_pear_template) {
513	/* Let's do a PEAR-less pear setup */
514	mkdir("$dist_dir/PEAR");
515	mkdir("$dist_dir/PEAR/go-pear-bundle");
516
517	/* grab the bootstrap script */
518	echo "Downloading go-pear\n";
519	copy("https://pear.php.net/go-pear.phar", "$dist_dir/PEAR/go-pear.php");
520
521	/* import the package list -- sets $packages variable */
522	include "pear/go-pear-list.php";
523
524	/* download the packages into the destination */
525	echo "Fetching packages\n";
526
527	foreach ($packages as $name => $version) {
528		$filename = "$name-$version.tgz";
529		$destfilename = "$dist_dir/PEAR/go-pear-bundle/$filename";
530		if (file_exists($destfilename))
531			continue;
532		$url = "http://pear.php.net/get/$filename";
533		echo "Downloading $name from $url\n";
534		flush();
535		copy($url, $destfilename);
536	}
537
538	echo "Download complete.  Extracting bootstrap files\n";
539
540	/* Now, we want PEAR.php, Getopt.php (Console_Getopt) and Tar.php (Archive_Tar)
541	 * broken out of the tarballs */
542	extract_file_from_tarball('PEAR', 'PEAR.php', "$dist_dir/PEAR/go-pear-bundle");
543	extract_file_from_tarball('Archive_Tar', 'Archive/Tar.php', "$dist_dir/PEAR/go-pear-bundle");
544	extract_file_from_tarball('Console_Getopt', 'Console/Getopt.php', "$dist_dir/PEAR/go-pear-bundle");
545}
546
547/* add extras from the template dir */
548if (file_exists($snapshot_template)) {
549	$items = glob("$snapshot_template/*");
550	print_r($items);
551
552	foreach ($items as $item) {
553		$bi = basename($item);
554		if (is_dir($item)) {
555			if ($bi == 'dlls' || $bi == 'symbols') {
556				continue;
557			} else if ($bi == 'PEAR') {
558				if ($use_pear_template) {
559					/* copy to top level */
560					copy_dir($item, "$dist_dir/$bi");
561				}
562			} else {
563				/* copy that dir into extras */
564				copy_dir($item, "$dist_dir/extras/$bi");
565			}
566		} else {
567			if ($bi == 'go-pear.bat') {
568				/* copy to top level */
569				copy($item, "$dist_dir/$bi");
570			} else {
571				/* copy to extras */
572				copy($item, "$dist_dir/extras/$bi");
573			}
574		}
575	}
576
577	/* copy c++ runtime */
578	$items = glob("$snapshot_template/dlls/*.CRT");
579
580	foreach ($items as $item) {
581		$bi = basename($item);
582		if (is_dir($item)) {
583			copy_dir($item, "$dist_dir/$bi");
584			copy_dir($item, "$dist_dir/ext/$bi");
585		}
586	}
587} else {
588	echo "WARNING: you don't have a snapshot template, your dist will not be complete\n";
589}
590
591make_phar_dot_phar($dist_dir);
592?>
593