summarylogtreecommitdiffstats
path: root/PKGBUILD
blob: b279d0785ff2b84bb18b7491b795d32229c50391 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# Maintainer: Steve Holvoet <linux@steho.be>
pkgname=ghidra-mcp-git
pkgver=6.0.0.r283.gb3f8dfe
pkgrel=2
pkgdesc="Production-ready Model Context Protocol server for Ghidra reverse engineering platform (latest git version)"
arch=('any')
url="https://github.com/bethington/ghidra-mcp"
license=('Apache-2.0')
depends=('ghidra' 'python' 'python-mcp')
makedepends=('maven' 'java-environment>=25' 'java-environment<27' 'git' 'python-build' 'python-installer' 'python-hatchling' 'python-wheel')
provides=("${pkgname%-git}")
conflicts=("${pkgname%-git}" "${pkgname}")
source=("git+${url}.git")
sha256sums=('SKIP')
install=ghidra-mcp.install

pkgver() {
  cd "${pkgname%-git}"
  git describe --long --tags --abbrev=7 | sed 's/^v//;s/\([^-]*-g\)/r\1/;s/-/./g'
}

prepare() {
  cd "${pkgname%-git}"

  # Standard Arch Linux path for Ghidra
  local _ghidra_home="/opt/ghidra"
  
  if [ -d "/usr/share/ghidra" ]; then
    _ghidra_home="/usr/share/ghidra"
  fi
  
  msg2 "Using Ghidra installation at: ${_ghidra_home}"

  if [ ! -f "${_ghidra_home}/Ghidra/application.properties" ]; then
    error "Ghidra properties not found at ${_ghidra_home}/Ghidra/application.properties"
    return 1
  fi

  local _detected_version
  _detected_version=$(grep "application.version=" "${_ghidra_home}/Ghidra/application.properties" | cut -d= -f2 | tr -d '\r')
  
  if [ -n "$_detected_version" ]; then
    msg2 "Found Ghidra version: ${_detected_version}"
    # Patch pom.xml to target installed Ghidra version
    sed -i "s|<ghidra.version>.*</ghidra.version>|<ghidra.version>${_detected_version}</ghidra.version>|g" pom.xml
  else
    error "Failed to detect Ghidra version from properties file."
    return 1
  fi

  # Ghidra is compiled with the JDK selected by its builder.  The MCP project
  # targets Java 21, but javac must still be new enough to read Ghidra's class
  # files.  A Java 25 compiler cannot consume Java 26 bytecode, for example.
  # Check the installed Ghidra bytecode against Maven's actual Java runtime so
  # the build fails with an actionable error instead of a cryptic javac error.
  local _ghidra_jar
  _ghidra_jar=$(find "${_ghidra_home}" -path '*/Framework/Generic/lib/Generic.jar' -print -quit)
  if [ -z "$_ghidra_jar" ]; then
    error "Could not find Ghidra Generic.jar for bytecode compatibility check."
    return 1
  fi

  local _ghidra_class_major
  _ghidra_class_major=$(javap -verbose -classpath "$_ghidra_jar" \
    ghidra.framework.Application 2>/dev/null |
    awk '/major version:/ { print $3; exit }')
  if [[ ! $_ghidra_class_major =~ ^[0-9]+$ ]]; then
    error "Could not determine the Ghidra class-file version from $_ghidra_jar."
    return 1
  fi

  local _maven_java_version _maven_java_major _maven_class_major
  _maven_java_version=$(mvn -version 2>/dev/null |
    awk -F': ' '/^Java version:/ { print $2; exit }')
  _maven_java_major=${_maven_java_version%%.*}
  if [[ ! $_maven_java_major =~ ^[0-9]+$ ]]; then
    error "Could not determine the Java version used by Maven."
    return 1
  fi
  _maven_class_major=$((_maven_java_major + 44))

  msg2 "Ghidra bytecode: major ${_ghidra_class_major}; Maven JDK: Java ${_maven_java_major}"
  if (( _maven_class_major < _ghidra_class_major )); then
    error "Maven is using Java ${_maven_java_major}, but Ghidra requires a JDK that can read class-file major ${_ghidra_class_major}."
    error "Select a compatible Java environment with archlinux-java before rebuilding."
    return 1
  fi

  # Upstream code calls dtm.remove(dataType, null), which requires
  # DataTypeManager.remove(DataType, TaskMonitor). ghidra-git >= 12.2
  # dropped that overload (it only forwarded to remove(DataType)), so the
  # 3 call sites fail to compile against the installed Ghidra. Rewrite them
  # to the single-arg form with identical semantics. Skip if the installed
  # Ghidra still provides the old overload.
  local _sm_jar
  _sm_jar=$(find "${_ghidra_home}" -name "SoftwareModeling.jar" | head -n 1)
  if [ -n "$_sm_jar" ] && ! javap -cp "$_sm_jar" ghidra.program.model.data.DataTypeManager 2>/dev/null \
      | grep -q "remove(ghidra.program.model.data.DataType, ghidra.util.task.TaskMonitor)"; then
    msg2 "Patching DataTypeService.java for removed remove(DataType, TaskMonitor) API"
    sed -i 's/dtm\.remove(dataType, null)/dtm.remove(dataType)/g; s/dtm\.remove(existing, null)/dtm.remove(existing)/g' \
      src/main/java/com/xebyte/core/DataTypeService.java
  fi
}

build() {
  cd "${pkgname%-git}"

  # Use the default system JDK (Ghidra JARs may require Java >= version used to compile them)

  # Determine Ghidra installation directory for dependencies
  local _ghidra_home="/opt/ghidra"
  if [ -d "/usr/share/ghidra" ]; then
     _ghidra_home="/usr/share/ghidra"
  fi
  export GHIDRA_INSTALL_DIR="${_ghidra_home}"

  # Determine target version from pom.xml (which we patched in prepare)
  local _ghidra_ver
  _ghidra_ver=$(grep "<ghidra.version>" pom.xml | sed 's|.*<ghidra.version>\(.*\)</ghidra.version>.*|\1|')

  # Install local Ghidra JAR dependencies required for compilation
  msg2 "Installing Ghidra dependencies to local Maven repository..."
  local _ghidra_jars=(
    "Generic"
    "SoftwareModeling"
    "Project"
    "Docking"
    "Decompiler"
    "Utility"
    "Base"
    "Gui"
    "FileSystem"
    "Help"
    "Emulation"
    "Debugger-api"
    "Framework-TraceModeling"
    "Debugger-rmi-trace"
    "DB"
    "Graph"
  )

  for _jar_name in "${_ghidra_jars[@]}"; do
    local _found_jar
    _found_jar=$(find "${GHIDRA_INSTALL_DIR}" -name "${_jar_name}.jar" | head -n 1)

    if [ -f "$_found_jar" ]; then
      mvn install:install-file \
        -Dfile="$_found_jar" \
        -DgroupId="ghidra" \
        -DartifactId="$_jar_name" \
        -Dversion="$_ghidra_ver" \
        -Dpackaging="jar" \
        -DgeneratePom=true \
        -Dmaven.repo.local="$srcdir/.m2" \
        -Dquiet=true
    else
      error "Required JAR '${_jar_name}.jar' not found in $GHIDRA_INSTALL_DIR"
      return 1
    fi
  done

  msg2 "Building project..."
  mvn clean package assembly:single -DskipTests -Dmaven.repo.local="$srcdir/.m2"
}

package() {
  cd "${pkgname%-git}"

  # 1. Install MCP Bridge Python package (pyproject.toml at project root)
  msg2 "Building and installing bridge Python package..."
  python -m build --wheel --no-isolation
  local _wheel
  _wheel=$(find dist -name "*.whl" | head -n 1)
  python -m installer --prefix=/usr --destdir="$pkgdir" "$_wheel"
  msg2 "Bridge package installed"

  # 2. Install Ghidra Extension ZIP
  local _ext_zip
  _ext_zip=$(find target -name "GhidraMCP-*.zip" | head -n 1)
  
  # Standard location for extensions
  local _ext_dest="$pkgdir/opt/ghidra/Extensions/Ghidra"
  
  if [ -f "$_ext_zip" ]; then
    install -d "$_ext_dest"
    install -m644 "$_ext_zip" "$_ext_dest/$(basename "$_ext_zip")"
    msg2 "Extension ZIP installed to $_ext_dest"
  else
    error "Build artifact not found."
    return 1
  fi

  # 3. Install Documentation
  install -Dm644 README.md "$pkgdir/usr/share/doc/${pkgname%-git}/README.md"
  if [ -f "CLAUDE.md" ]; then
    install -Dm644 CLAUDE.md "$pkgdir/usr/share/doc/${pkgname%-git}/CLAUDE.md"
  fi

  # 4. Clean up __pycache__ from package
  find "$pkgdir" -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
}