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