In order to make it easier to modify parts of Curie independently and make it possible to combine the codebase with other modules in new ways, it was necessary to split Curie into separate modules.
For example, if we want to create a new Gtk3-based application, then pulling in a module which contains helper functions for setting up Gtk3 could be shared amongst all Gtk3-based applications instead of copying the code into each new application.
Renaming the quick-and-dirty way
The first step towards refactoring is renaming the modules that make up Curie.
Since all of the code is placed under the Renard::Curie
namespace, a new
namespace must be created and all the non-Curie specific modules need to be
placed under that. This new namespace is Renard::Incunabula
and the name
changes are specified in the following subsection.
Package renaming by final repository
project-renard/p5-Renard-Incunabula
The p5-Renard-Incunabula repository is for common code that all tools
can use. In particular, it contains the common development setup (such as
Function::Parameters
, error types,
and data types) and generic document/page models.
Original | Renamed |
---|---|
Renard::Curie::Setup |
Renard::Incunabula::Common::Setup |
Renard::Curie::Types |
Renard::Incunabula::Common::Types |
Renard::Curie::Error |
Renard::Incunabula::Common::Error |
Renard::Curie::Model::Document |
Renard::Incunabula::Document |
Renard::Curie::Model::Outline |
Renard::Incunabula::Outline |
Renard::Curie::Model::Document::Role::FromFile |
Renard::Incunabula::Document::Role::FromFile |
Renard::Curie::Model::Document::Role::Pageable |
Renard::Incunabula::Document::Role::Pageable |
Renard::Curie::Model::Document::Role::Cacheable |
Renard::Incunabula::Document::Role::Cacheable |
Renard::Curie::Model::Document::Role::Renderable |
Renard::Incunabula::Document::Role::Renderable |
Renard::Curie::Model::Document::Role::Boundable |
Renard::Incunabula::Document::Role::Boundable |
Renard::Curie::Model::Document::Role::Outlineable |
Renard::Incunabula::Document::Role::Outlineable |
Renard::Curie::Model::Page::Role::CairoRenderableFromPNG |
Renard::Incunabula::Page::Role::CairoRenderableFromPNG |
Renard::Curie::Model::Page::Role::Bounds |
Renard::Incunabula::Page::Role::Bounds |
Renard::Curie::Model::Page::Role::BoundsFromCairoImageSurface |
Renard::Incunabula::Page::Role::BoundsFromCairoImageSurface |
Renard::Curie::Model::Page::Role::CairoRenderable |
Renard::Incunabula::Page::Role::CairoRenderable |
Renard::Curie::Model::Page::CairoImageSurface |
Renard::Incunabula::Page::CairoImageSurface |
Renard::Curie::Model::Page::RenderedFromPNG |
Renard::Incunabula::Page::RenderedFromPNG |
Renard::Curie::Model::Document::CairoImageSurface |
Renard::Incunabula::Format::Cairo::ImageSurface::Document |
project-renard/p5-Renard-Incunabula-MuPDF-mutool
The p5-Renard-Incunabula-MuPDF-mutool repository wraps the mutool
command line
program from MuPDF in order to retrieve page bounding box information and
to render document pages as PNG images.
Original | Renamed |
---|---|
Renard::Curie::Data::PDF |
Renard::Incunabula::MuPDF::mutool |
project-renard/p5-Renard-Incunabula-Format-PDF
The p5-Renard-Incunabula-Format-PDF repository contains models for working with PDF files. It currently uses p5-Renard-Incunabula-MuPDF-mutool as the backend, but there are plans to use other backends.
Original | Renamed |
---|---|
Renard::Curie::Model::Document::PDF |
Renard::Incunabula::Format::PDF::Document |
Renard::Curie::Model::Page::PDF |
Renard::Incunabula::Format::PDF::Page |
project-renard/p5-Renard-Incunabula-Frontend-Gtk3
The p5-Renard-Incunabula-Frontend-Gtk3 repository contains helper
functions (e.g., shims for older versions of the gtk+3
library) and roles
(e.g., loading components from Glade GUI builder XML files) for Gtk3.
Original | Renamed |
---|---|
Renard::Curie::Helper |
Renard::Incunabula::Frontend::Gtk3::Helper |
Renard::Curie::Component::Role::FromBuilder |
Renard::Incunabula::Frontend::Gtk3::Component::Role::FromBuilder |
Renard::Curie::Component::Role::UIFileFromPackageName |
Renard::Incunabula::Frontend::Gtk3::Component::Role::UIFileFromPackageName |
Creating the new repositories
In order to create new repositories for these files, it is necessary to first
make sure that we only keep the commits in the history that touch those files.
The first step is to create clones of the original curie repo for each
of the new repositories. To do so, .nfo
files are created that contain the
information from the above renaming tables. In
project-renard/rename/p5-Renard-Incunabula-Format-PDF.nfo
, this looks like
s,Renard::Curie::Model::Document::PDF,Renard::Incunabula::Format::PDF::Document, s,Renard::Curie::Model::Page::PDF,Renard::Incunabula::Format::PDF::Page,
Each original package name is listed in a sed(1)
style substitution using
commas as the delimiters (as opposed to the traditional forward-slash since
this conflicts with file paths).
We then run the following script for each repo in the form ./repo-process.sh
p5-Renard-Incunabula-Format-PDF
.
In project-renard/rename/repo-process.sh
:
#!/bin/sh
REPO="$1";
[ -z "$REPO" ] && echo "no repo" && exit 1
cd ~/sw_projects/project-renard/$REPO/$REPO
PATHS_TO_KEEP=`~/sw_projects/project-renard/rename/files-to-keep.sh ~/sw_projects/project-renard/rename/$REPO.nfo | tr '\n' ' '`
if [ "$REPO" = "p5-Renard-Incunabula" ]; then
PATHS_TO_KEEP="$PATHS_TO_KEEP t/lib/CurieTestHelper.pm"
fi
echo $PATHS_TO_KEEP
BRANCH_TO_EXTRACT_FROM='master'
git filter-branch -f --index-filter \
"git rm --ignore-unmatch --cached -qr -- . && git reset -q \$GIT_COMMIT -- $PATHS_TO_KEEP" \
--prune-empty -- $BRANCH_TO_EXTRACT_FROM
In project-renard/rename/files-to-keep.sh
:
#!/bin/sh
FILE_OF_REPLACEMENTS="$1"
grep -o 's,*,' $FILE_OF_REPLACEMENTS \
| grep -o 'Renard*' \
| sed -e 's,::,/,g' \
| sed 's,\(.*\),lib/\1.pm t/\1.t,' \
| sed 's/ /\n/' | sort -u \
| xargs -I{} bash -c '[ -f "{}" ] && ls {}' \
| xargs -n1 git log --name-only --format=format: --follow -- \
| sort -u | grep -v '^$'
What this does is keep all the git history for the files that are under the
lib/
and t/
directories that refer to that package. So for the line
s,Renard::Curie::Model::Document::PDF,Renard::Incunabula::Format::PDF::Document,
it would keep the files lib/Renard/Curie/Model/Document/PDF.pm
and
t/Renard/Curie/Model/Document/PDF.t
and any of the commits that contained
those files or their previous names (by using git log --follow
).
Renaming the packages
The previous step only moves the files into the new repositories, but does not
rename their contents. The following script changes the contents of
each repository by calling ./do-replacements.sh
.
In project-renard/rename/do-replacements.sh
:
#!/bin/sh
DIR_INFO="$HOME/sw_projects/project-renard/rename"
REPO_DIRS=`ls $DIR_INFO/*.nfo | xargs -n1 basename | sed 's/.nfo$//'`
SED_LIBS=`cat $DIR_INFO/*.nfo | awk '{ print length, $0 }'| sort -nr | cut -d' ' -f2 | tr '\n' ';'`
SED_FILES=`echo $SED_LIBS | sed -e 's,::,/,g'`
#echo $SED_LIBS
#echo $SED_FILES
for repo in $REPO_DIRS curie; do
REPO_DIR="$HOME/sw_projects/project-renard/$repo/$repo"
REPO_FILES=`find $REPO_DIR/lib $REPO_DIR/t -type f`;
perl -pi -e "$SED_LIBS" $REPO_FILES
rename -n "$SED_FILES" $REPO_FILES \
| sed -e 's,rename(,smv ,' -e 's,)$,,' -e 's/,//' \
| sh
done
for repo in $REPO_DIRS; do
REPO_DIR="$HOME/sw_projects/project-renard/$repo/$repo"
cd $REPO_DIR
git add .
git tag -d $(git tag -l)
done
What this does is take all of the .nfo
files that contain those
substitutions and applies those substitutions in each repository (i.e., the new
repositories and curie). Not only does it apply those substitutions to the
contents of the files, but also to the full paths using the rename(1p)
command. So the previously mentioned files are renamed as follows:
Original | Renamed |
---|---|
lib/Renard/Curie/Model/Document/PDF.pm |
lib/Renard/Incunabula/Format/PDF/Document.pm |
t/Renard/Curie/Model/Document/PDF.t |
t/Renard/Incunabula/Format/PDF/Document.t |
In addition, for all the new repositories (i.e., not curie), the tags are deleted since the older release versions do not apply.
Finally, the package in t/lib/CurieTestHelper.pm
has been turned into the
library package Renard::Incunabula::Devel::TestHelper
in
p5-Renard-Incunabula. This allows it to be shared by the testing code
in all the repositories.
Tracking dependencies and other developer maintenance
Since each of the new repositories are starting off without any of the
developer maintenance setup that curie has (such as the list of native
dependencies and CI configuration), it is necessary to make sure that they
start off with a basic skeleton. This is contained in the skeleton
directory of devops.
In particular, the following files are special because they are used to specify dependencies:
cpanfile-git
The maint/cpanfile-git
file contains the repository Git URLs and branch
name for any of the other Project Renard dependencies that are needed to
run the code in the current repository. Having this allows for testing
changes in those dependencies by just switching to another branch name. For
example, the p5-Renard-Incunabula-Frontend-Gtk3 cpanfile-git
file
contains
requires 'Renard::Incunabula',
git => 'https://github.com/project-renard/p5-Renard-Incunabula.git',
branch => 'master';
which means that when building under a CI environment, the
Renard-Incunabula
distribution will be installed from the Git repository
before it is pulled from CPAN. In fact, this approach allowed the tests to
run under the CI environment before all of the dependencies were uploaded to
CPAN as long as the following environment variable was set
- export PERL_CPANM_OPT="--skip-satisfied"
so that cpanm
would not exit if the required module was installed but it
could not find the required modules on CPAN.
In addition to installing the code from source in the CI environment, the Git repository provides a commit SHA that can be used to decide whether to reinstall from source or keep the version that was previously installed at that SHA. This allows for shorter CI runs because modules that take a while to build such as p5-Alien-MuPDF do not need to be rebuilt.
devops.yml
The maint/devops.yml
file specifies the native dependencies that are
needed to run the code in the current repository. For
example, the p5-Renard-Incunabula-Frontend-Gtk3 devops.yml
file
contains
--- native: debian: packages: - pkg-config - libgirepository1.0-dev - gobject-introspection - libgtk-3-dev macos-homebrew: packages: - gtk+3 - gtk-mac-integration - gnome-icon-theme msys2-mingw64: packages: - mingw-w64-x86_64-perl - mingw-w64-x86_64-gobject-introspection - mingw-w64-x86_64-cairo - mingw-w64-x86_64-gtk3
which specifies all the gtk+3
packages needed by that repository. This is
used to install each module from source so it is not necessary to repeat the
native dependencies of each of the repositories specified in cpanfile-git
since the devops.yml
files of those repositories will specify what they need.
Conclusion
Moving the code into separate repositories has not yet been used to create new applications, but it has made library code more modular and opened the way for more flexible approaches such as the ability to incorporate multiple rendering backends for a given file type such as PDF.
While the refactoring code is not the cleanest approach, it does cover all the needs for this job. It may need to be revisited later to make a more general-purpose tool.