Commit d51bdcf60dd7fa1a34895925cfc88fd64dba4e02

Authored by Jay Berkenbilt
1 parent 6e580e24

Enhance Windows build for local dev

* Remove dependency on `perl`, now only required for maintenance
  activities and running the test suite.
* Exercise building qpdf and running executables from JetBrains CLion
  without any special additional tooling beyond pre-built external
  libraries.

This replaces the `copy_dlls` with a powershell script, written mostly
by ChatGPT, starting from the bash script below. The copy_dlls script
had a lot of logic that was no longer needed.

```bash
#!/bin/bash
set -eo pipefail
exe="$1"
dest="$2"
mingw_bin_dir="$3"
if [[ $mingw_bin_dir == "" ]]; then
    echo >&2 "Usage: $(basename $0) exe dest mingw-bin-dir"
    exit 2
fi

get_dlls() {
    objdump -p "$1" | grep 'DLL Name:' | awk '{print $NF}'
}
declare -a dlls
dlls=($(get_dlls "$exe"))
declare -A seen
while [[ ${#dlls[@]} -gt 0 ]]; do
    i="${dlls[0]}"
    dlls=("${dlls[@]:1}")
    if [[ ${seen[$i]} == 1 ]]; then
        continue
    fi
    seen[$i]=1
    full="$mingw_bin_dir/$i"
    if [[ -f "$full" ]]; then
        cp "$full" $dest/
        dlls+=($(get_dlls "$full"))
    fi
done
```
CMakeLists.txt
... ... @@ -10,9 +10,8 @@ project(qpdf
10 10 VERSION 12.1.1
11 11 LANGUAGES C CXX)
12 12  
13   -# Enable correct rpath handling for MacOSX
14   -cmake_policy(SET CMP0042 NEW)
15   -# Honor CMAKE_REQUIRED_LIBRARIES when checking for include files
  13 +# Honor CMAKE_REQUIRED_LIBRARIES when checking for include files. This
  14 +# can be removed in cmake >= 3.17.
16 15 cmake_policy(SET CMP0075 NEW)
17 16  
18 17 # *** OPTIONS ***
... ... @@ -246,6 +245,7 @@ if(MSVC)
246 245 list(APPEND WINDOWS_WMAIN_LINK -link wsetargv.obj)
247 246 endif()
248 247 if(MINGW)
  248 + get_filename_component(MINGW_BIN_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY)
249 249 execute_process(
250 250 COMMAND gcc --print-file-name=CRT_glob.o
251 251 OUTPUT_VARIABLE CRT_GLOB_O
... ... @@ -387,6 +387,11 @@ message(STATUS " QTC test coverage: ${ENABLE_QTC}")
387 387 message(STATUS " include future changes: ${FUTURE}")
388 388 message(STATUS " system: ${CPACK_SYSTEM_NAME}")
389 389 message(STATUS "")
  390 +if(MINGW)
  391 + message(STATUS "MinGW compiler: ${CMAKE_CXX_COMPILER}")
  392 + message(STATUS "MinGW bin dirrectory: ${MINGW_BIN_DIR}")
  393 + message(STATUS "")
  394 +endif()
390 395 message(STATUS "*** Options Summary ***")
391 396 foreach(PROP
392 397 COMPILE_OPTIONS INTERFACE_COMPILE_OPTIONS
... ...
README-windows.md
  1 +Quick Start with JetBrains CLion
  2 +================================
  3 +
  4 +The following *should* work but has not been tested on a completely clean system with CLion. It may
  5 +work with other "batteries-included" IDEs as well.
  6 +
  7 +* Install `external-libs` from [prebuilt static external libraries from the qpdf/external-libs
  8 + github repository](https://github.com/qpdf/external-libs/releases) by unzipping the binary
  9 + distribution into an otherwise clean source tree.
  10 +* Using the default toolchain, you can create a cmake build of type *other than Debug*. A `Debug`
  11 + build will not work with the external libraries since debug versions are not redistributable. If
  12 + you want a Debug build, you'll have to build the external libraries yourself. The external-libs
  13 + repo above can be a hint, or you can get them from other sources.
  14 +* If you have MSVC, you can enable one of the `msvc` presets that you should see when you edit CMake
  15 + configurations.
  16 +
  17 +In any of these, it should work to build and run the executables from the IDE. Note that, if you
  18 +start a terminal from CLion and mingw is not in your path, the executables built my mingw won't run.
  19 +If mingw is in your path, it should work. You can also start a mingw64 shell. The executables should
  20 +work from there. This works because the cmake configuration copies the qpdf DLL into the bin
  21 +directory. If you want the other executables to work, you should add the `libqpdf` directory of your
  22 +build directory to your path or disable shared libraries. For more details, consult the qpdf manual
  23 +and the rest of this file.
  24 +
  25 +Additional dependencies are required for running tests. For the foreseeable future, that requires
  26 +msys2, though this may eventually not be the case.
  27 +
1 28 Common Setup
2 29 ============
3 30  
... ...
copy_dlls deleted
1   -#!/usr/bin/env perl
2   -require 5.008;
3   -use warnings;
4   -use strict;
5   -use File::Basename;
6   -use File::Path qw(make_path);
7   -
8   -my $whoami = basename($0);
9   -
10   -usage() unless @ARGV == 3;
11   -my ($file, $libqpdf, $destdir) = @ARGV;
12   -my $filedir = dirname($file);
13   -
14   -my $sep = ($^O eq 'MSWin32' ? ';' : ':');
15   -my @path = ($filedir, '.', split($sep, $ENV{'PATH'}));
16   -foreach my $var (qw(LIB))
17   -{
18   - if (exists $ENV{$var})
19   - {
20   - push(@path, split($sep, $ENV{$var}));
21   - }
22   -}
23   -
24   -my $format = undef;
25   -my @to_find = get_dlls($file);
26   -
27   -my %final = ();
28   -my @notfound = ();
29   -
30   -while (@to_find)
31   -{
32   - my $dll = shift(@to_find);
33   - my $found = 0;
34   - foreach my $dir ($libqpdf, @path)
35   - {
36   - if ((-f "$dir/$dll") && is_format("$dir/$dll", $format))
37   - {
38   - if (! exists $final{$dll})
39   - {
40   - if ($dir ne $libqpdf)
41   - {
42   - $final{$dll} = "$dir/$dll";
43   - }
44   - push(@to_find, get_dlls("$dir/$dll"));
45   - }
46   - $found = 1;
47   - last;
48   - }
49   - }
50   - if (! $found)
51   - {
52   - push(@notfound, $dll);
53   - }
54   -}
55   -if (@notfound)
56   -{
57   - die "$whoami: can't find the following dlls: " .
58   - join(', ', @notfound), "\n";
59   -}
60   -
61   -make_path($destdir);
62   -foreach my $dll (sort keys (%final))
63   -{
64   - my $f = $final{$dll};
65   - $f =~ s,\\,/,g;
66   - print "Copying $f to $destdir\n";
67   - system("cp -p '$f' '$destdir'") == 0 or
68   - die "$whoami: copy $f to $destdir failed\n";
69   -}
70   -
71   -sub get_dlls
72   -{
73   - my @result = ();
74   - my $exe = shift;
75   - open(O, "objdump -p \"$exe\"|") or die "$whoami: can't run objdump\n";
76   - while (<O>)
77   - {
78   - if (m/^\s+DLL Name:\s+(.+\.dll)/i)
79   - {
80   - my $dll = $1;
81   - $dll =~ tr/A-Z/a-z/;
82   - next if $dll =~ m/^(kernel32|user32|msvcrt|advapi32)\.dll$/;
83   - next if $dll =~ m/^(api-ms-win.*|ws2_32|crypt32|bcrypt)\.dll$/;
84   - push(@result, $dll);
85   - }
86   - elsif (m/^Magic.*\((PE.+?)\)/)
87   - {
88   - $format = $1;
89   - }
90   - }
91   - close(O);
92   - if (! defined $format)
93   - {
94   - die "$whoami: can't determine format of $exe\n";
95   - }
96   - @result;
97   -}
98   -
99   -sub is_format
100   -{
101   - my ($file, $format) = @_;
102   - $file =~ s,\\,/,g;
103   - # Special case: msvc*.dll seem to be able to behave both as 32-bit
104   - # and 64-bit DLLs. Either that, or this logic is wrong for those
105   - # DLLs and it doesn't matter because they're already installed on
106   - # my test system (which doesn't have msvc installed on it).
107   - if ($file =~ m,/msvc,i)
108   - {
109   - return 1;
110   - }
111   - my $result = 0;
112   - my $file_format = `file "$file"`;
113   - print "$file $format $file_format\n";
114   - if ($? == 0)
115   - {
116   - if ($file_format =~ m/\Q${format}\E executable/)
117   - {
118   - $result = 1;
119   - }
120   - }
121   - $result;
122   -}
123   -
124   -sub usage
125   -{
126   - die "Usage: $whoami {exe|dll} libqpdf-dir destdir\n";
127   -}
copy_dlls.ps1 0 → 100755
  1 +param (
  2 + [string]$exe,
  3 + [string]$dest,
  4 + [string]$mingwBinDir
  5 +)
  6 +
  7 +function Get-DllDependencies {
  8 + param([string]$binary)
  9 + & objdump.exe -p $binary 2>$null |
  10 + Select-String 'DLL Name:' |
  11 + ForEach-Object { ($_ -split ':')[1].Trim() }
  12 +}
  13 +
  14 +if (-not $exe -or -not $dest -or -not $mingwBinDir) {
  15 + Write-Error "Usage: $(Split-Path -Leaf $MyInvocation.MyCommand.Name) -exe exe -dest dest -mingwBinDir mingw-bin-dir"
  16 + exit 2
  17 +}
  18 +
  19 +New-Item -ItemType Directory -Path $dest -Force | Out-Null
  20 +$dlls = [System.Collections.Generic.Queue[string]]::new()
  21 +[void]$dlls.Enqueue($exe)
  22 +$seen = @{}
  23 +foreach ($dll in Get-DllDependencies $exe) {
  24 + $dlls.Enqueue($dll)
  25 +}
  26 +
  27 +while ($dlls.Count -gt 0) {
  28 + $item = $dlls.Dequeue()
  29 + $basename = if (Test-Path $item) { Split-Path $item -Leaf } else { $item }
  30 +
  31 + if ($seen[$basename]) {
  32 + continue
  33 + }
  34 + $seen[$basename] = $true
  35 + $full = Join-Path $mingwBinDir $basename
  36 + if (Test-Path $full -PathType Leaf) {
  37 + Copy-Item $full -Destination $dest -Force
  38 + foreach ($dll in Get-DllDependencies $full) {
  39 + $dlls.Enqueue($dll)
  40 + }
  41 + }
  42 +}
... ...
job.sums
1 1 # Generated by generate_auto_job
2   -CMakeLists.txt b9d848a8e701c06278371deba02cd67cc3db432bf4a36730a4d829d04f470e2d
  2 +CMakeLists.txt e66fe7a418c9d241a577e1f811121235e288073a00744976accdc3f867fec620
3 3 generate_auto_job f64733b79dcee5a0e3e8ccc6976448e8ddf0e8b6529987a66a7d3ab2ebc10a86
4 4 include/qpdf/auto_job_c_att.hh 4c2b171ea00531db54720bf49a43f8b34481586ae7fb6cbf225099ee42bc5bb4
5 5 include/qpdf/auto_job_c_copy_att.hh 50609012bff14fd82f0649185940d617d05d530cdc522185c7f3920a561ccb42
... ...
qpdf/CMakeLists.txt
... ... @@ -72,13 +72,26 @@ if(MINGW)
72 72 set(ONE_GNU_DLL extra-dlls/libstdc++-6.dll)
73 73 add_custom_command(OUTPUT ${ONE_GNU_DLL}
74 74 COMMAND
75   - perl ${qpdf_SOURCE_DIR}/copy_dlls
76   - qpdf.exe
77   - ${CMAKE_BINARY_DIR}/libqpdf
78   - extra-dlls)
  75 + pwsh.exe -NoProfile -ExecutionPolicy Bypass -Command
  76 + "${qpdf_SOURCE_DIR}/copy_dlls.ps1"
  77 + -exe $<TARGET_FILE:qpdf>
  78 + -dest "${CMAKE_CURRENT_BINARY_DIR}/extra-dlls"
  79 + -mingwBinDir "${MINGW_BIN_DIR}"
  80 + VERBATIM
  81 + COMMAND_EXPAND_LISTS
  82 + DEPENDS qpdf)
79 83 add_custom_target(extra_dlls ALL DEPENDS ${ONE_GNU_DLL})
80 84 add_dependencies(extra_dlls qpdf)
81 85 if(BUILD_SHARED_LIBS)
  86 + set(LIBQPDF_NAME get_filename_component($<TARGET_FILE:libqpdf> NAME))
  87 + add_custom_command(
  88 + TARGET qpdf POST_BUILD
  89 + COMMAND
  90 + ${CMAKE_COMMAND} -E copy_if_different
  91 + $<TARGET_FILE:libqpdf>
  92 + $<TARGET_FILE_DIR:qpdf>
  93 + VERBATIM
  94 + COMMAND_EXPAND_LISTS)
82 95 set(EXTRA_DLL_COMPONENT ${COMPONENT_LIB})
83 96 else()
84 97 set(EXTRA_DLL_COMPONENT ${COMPONENT_CLI})
... ...